package org.hackystat.devcathlon.engine.event.impl;

import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.devcathlon.engine.event.Event;
import org.hackystat.devcathlon.engine.event.EventException;
import org.hackystat.devcathlon.engine.event.EventScore;
import org.hackystat.devcathlon.engine.team.Team;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.client.SensorBaseClientException;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.tstamp.Tstamp;
import org.hackystat.devcathlon.engine.match.Match;

/**
 * Penalizes teams that work significantly more near the end of a match.
 * <p>
 * Requires the following properties in the configuration.xml file for this event:
 * <ul>
 * <li>WakeupInterval: How frequently this event is invoked, and how far back it looks for data.
 * <li>BaseScoreWeight: An integer multiplied by the base score to get the base points.
 * <li>LastDays: An integer indicating length of the penalty period.
 * <li>ConditionsCount: An integer indicating the number of conditions that trigger the penalty.
 * <li>PercentageThreshhold: An integer indicating the excess percentage to trigger the penalty.
 * </ul>
 * </p>
 * <p>
 * Specification: <br>
 * <br>
 * <i>5</i> points are deducted if the team exceeds any two of the following categories by
 * <i>50%</i> over the last <i>3 days</i>:
 * <ul>
 * <li>Average DevTime per day (throughout entire match)</li>
 * <li>Number of builds per day (throughout entire match)</li>
 * <li>Number of commits per day (throughout entire match)</li>
 * </ul>
 * </p>
 * 
 * All italicized fields are configurable through the configurations.xml
 * 
 * @author John Ly, John Zhou
 * 
 */
public class DontWaitUntilLastMin extends Event {

  /** Support serialization. */
  private static final long serialVersionUID = 1L;
  /** Name of the event. */
  public static final String eventName = "Don't Wait Until The Last Minute";
  /** Description of the event. */
  public static final String description = "Penalize teams that work significantly more near the "
      + "end of a match.";
  /** Property key for the match last days count. */
  public static final String lastDaysKey = "LastDays";
  /** Property key for the penalty condition count. */
  public static final String conditionsKey = "ConditionsCount";
  /** Property key for the percent threshold for the conditions. */
  public static final String percentThreshholdKey = "PercentageThreshold";
  /** Count for the last days period. */
  private int lastDaysCount;
  /** Number of conditions that fail to meet requirements. */
  private int conditionsCount;
  /** Percentage upper threshold for conditions not met. */
  private int percentThreshhold;

  /**
   * Creates a new event for Don'tWaitUntilLastMinute. Ensures all required properties are
   * available.
   * 
   * @throws EventException If the configuration cannot be created.
   */
  public DontWaitUntilLastMin() throws EventException {
    super(eventName, description);
    try {
      lastDaysCount = Integer.parseInt(configManager.getProperty(eventName, lastDaysKey));
      conditionsCount = Integer.parseInt(configManager.getProperty(eventName, conditionsKey));
      percentThreshhold = Integer.parseInt(configManager.getProperty(eventName,
          percentThreshholdKey));
    }
    catch (Exception e) {
      throw new EventException("Error configuring properties", e);
    }
  }

  /**
   * Returns the total number of points for this event.
   * 
   * @param score The event score.
   * @return The total points for this event given the current weights.
   */
  @Override
  public int computePoints(EventScore score) {
    return (score.getBaseScore() * this.baseScoreWeight);
  }

  /**
   * Creates an EventScore instance for DontWaitUntilLastMin.
   * 
   * Penalizes team by <i>5</i> points if the team exceeds any two of the following categories by
   * <i>50%</i> over the last <i>3 days</i>:
   * <ul>
   * <li>Average DevTime per day (throughout entire match)</li>
   * <li>Number of builds per day (throughout entire match)</li>
   * <li>Number of commits per day (throughout entire match)</li>
   * </ul>
   * 
   * @param match The match of interest.
   * @param team The team of interest.
   * @param timestamp The time at which this event is being run.
   * @return The EventScore.
   * @throws Exception If there is an error with the match
   */
  @Override
  public EventScore computeScore(Match match, Team team, XMLGregorianCalendar timestamp)
      throws Exception {
    EventScore score = new EventScore(timestamp, match, team);

    Project project = match.getProject(team);
    String projectName = project.getName();
    String owner = project.getOwner();
    String buildSdt = "Build";
    String commitSdt = "Commit";
    String devEventSdt = "DevEvent";

    int totalBuilds = 0;
    int totalCommits = 0;
    long totalDevTime = 0;
    int currentDayBuilds = 0;
    int currentDayCommits = 0;
    long currentDayDevTime = 0;

    XMLGregorianCalendar startOfToday = Tstamp.incrementDays(timestamp, -1);
    XMLGregorianCalendar startOfPenaltyPeriod = Tstamp.incrementDays(match.getEndDate(),
        lastDaysCount * -1);

    // if not within the penalty period, return score of zero
    if (Tstamp.greaterThan(startOfPenaltyPeriod, timestamp)) {
      score.setExplanation("No penalty because not within penalty period.");
      score.setBaseScore(0);
      return score;
    }

    SensorBaseClient client = match.getSensorBaseClient(team);
    SensorDataIndex index;

    HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon").info(
        String.format("Getting commit, build, and devtime sensor data for match %s and team %s",
            match.getName(), team.getName()));
    try {
      // retrieve all build, commit, devtime data prior to penalty period
      index = client.getProjectSensorData(owner, projectName, match.getStartDate(),
          startOfPenaltyPeriod, buildSdt);
      totalBuilds = index.getSensorDataRef().size();

      index = client.getProjectSensorData(owner, projectName, match.getStartDate(),
          startOfPenaltyPeriod, commitSdt);
      totalCommits = index.getSensorDataRef().size();

      index = client.getProjectSensorData(owner, projectName, match.getStartDate(),
          startOfPenaltyPeriod, devEventSdt);
      totalDevTime = index.getSensorDataRef().size() * 5;

      // retrieve data for the current day
      index = client.getProjectSensorData(owner, projectName, startOfToday, timestamp, buildSdt);
      currentDayBuilds = index.getSensorDataRef().size();

      index = client.getProjectSensorData(owner, projectName, startOfToday, timestamp, commitSdt);
      currentDayCommits = index.getSensorDataRef().size();

      index = client.getProjectSensorData(owner, projectName, startOfToday, timestamp, devEventSdt);
      currentDayDevTime = index.getSensorDataRef().size() * 5;
    }
    catch (SensorBaseClientException e) {
      throw new EventException(String.format("Error retrieving data for project %s.", projectName),
          e);
    }

    // compute the average of values
    int daysBeforePenaltyPeriod = Tstamp.daysBetween(match.getStartDate(), startOfPenaltyPeriod);
    double averageBuilds = (double) totalBuilds / daysBeforePenaltyPeriod;
    double averageCommits = (double) totalCommits / daysBeforePenaltyPeriod;
    double averageDevTime = (double) totalDevTime / daysBeforePenaltyPeriod;

    int violatedCategories = 0;
    double percentage = (1 + (percentThreshhold * 0.01));

    if (currentDayBuilds > averageBuilds * percentage) {
      violatedCategories++;
    }
    if (currentDayCommits > averageCommits * percentage) {
      violatedCategories++;
    }
    if (currentDayDevTime > averageDevTime * percentage) {
      violatedCategories++;
    }

    if (violatedCategories >= conditionsCount) {
      score.setExplanation("Deducted points for working over " + percentThreshhold
          + "% of daily average");
      score.setBaseScore(score.getBaseScore() - 1);
    }
    else {
      score.setExplanation("No penalty: Team did not exceed threshhold");
      return score;
    }
    return score;
  }

}
